﻿using System;

namespace Patterns.State {
    public static class AppliedExample {

        public static void Run() {
            // Open a new account
            Account account = new Account("Jim Johnson");

            // Apply financial transactions
            account.Deposit(500.0);
            account.Deposit(300.0);
            account.Deposit(550.0);
            account.PayInterest();
            account.Withdraw(2000.00);
            account.Withdraw(1100.00);
        }
    }

    // "State"
    abstract class State {
        protected Account account;
        protected double balance;

        protected double interest;
        protected double lowerLimit;
        protected double upperLimit;

        // Properties
        public Account Account {
            get { return account; }
            set { account = value; }
        }

        public double Balance {
            get { return balance; }
            set { balance = value; }
        }

        public abstract void Deposit(double amount);
        public abstract void Withdraw(double amount);
        public abstract void PayInterest();
    }

    // "ConcreteState"
    // Account is overdrawn
    class RedState : State {
        double serviceFee;

        // Constructor
        public RedState(State state) {
            this.balance = state.Balance;
            this.account = state.Account;
            Initialize();
        }

        private void Initialize() {
            // Should come from a datasource
            interest = 0.0;
            lowerLimit = -100.0;
            upperLimit = 0.0;
            serviceFee = 15.00;
        }

        public override void Deposit(double amount) {
            balance += amount;
            StateChangeCheck();
        }

        public override void Withdraw(double amount) {
            amount = amount - serviceFee;
            Console.WriteLine("No funds available for withdrawal!");
        }

        public override void PayInterest() {
            // No interest is paid
        }

        private void StateChangeCheck() {
            if (balance > upperLimit) {
                account.State = new SilverState(this);
            }
        }
    }

    // "ConcreteState"
    // Silver is non-interest bearing state
    class SilverState : State {
        // Overloaded constructors

        public SilverState(State state)
            :
          this(state.Balance, state.Account) {
        }

        public SilverState(double balance, Account account) {
            this.balance = balance;
            this.account = account;
            Initialize();
        }

        private void Initialize() {
            // Should come from a datasource
            interest = 0.0;
            lowerLimit = 0.0;
            upperLimit = 1000.0;
        }

        public override void Deposit(double amount) {
            balance += amount;
            StateChangeCheck();
        }

        public override void Withdraw(double amount) {
            balance -= amount;
            StateChangeCheck();
        }

        public override void PayInterest() {
            balance += interest * balance;
            StateChangeCheck();
        }

        private void StateChangeCheck() {
            if (balance < lowerLimit) {
                account.State = new RedState(this);
            } else if (balance > upperLimit) {
                account.State = new GoldState(this);
            }
        }
    }

    // "ConcreteState"
    // Interest bearing state
    class GoldState : State {
        // Overloaded constructors
        public GoldState(State state)
            : this(state.Balance, state.Account) {
        }

        public GoldState(double balance, Account account) {
            this.balance = balance;
            this.account = account;
            Initialize();
        }

        private void Initialize() {
            // Should come from a database
            interest = 0.05;
            lowerLimit = 1000.0;
            upperLimit = 10000000.0;
        }

        public override void Deposit(double amount) {
            balance += amount;
            StateChangeCheck();
        }

        public override void Withdraw(double amount) {
            balance -= amount;
            StateChangeCheck();
        }

        public override void PayInterest() {
            balance += interest * balance;
            StateChangeCheck();
        }

        private void StateChangeCheck() {
            if (balance < 0.0) {
                account.State = new RedState(this);
            } else if (balance < lowerLimit) {
                account.State = new SilverState(this);
            }
        }
    }

    // "Context"
    class Account {
        private State state;
        private string owner;

        // Constructor
        public Account(string owner) {
            // New accounts are 'Silver' by default
            this.owner = owner;
            state = new SilverState(0.0, this);
        }

        // Properties
        public double Balance {
            get { return state.Balance; }
        }

        public State State {
            get { return state; }
            set { state = value; }
        }

        public void Deposit(double amount) {
            state.Deposit(amount);
            Console.WriteLine("Deposited {0:C} --- ", amount);
            Console.WriteLine(" Balance = {0:C}", this.Balance);
            Console.WriteLine(" Status = {0}\n",
              this.State.GetType().Name);
            Console.WriteLine("");
        }

        public void Withdraw(double amount) {
            state.Withdraw(amount);
            Console.WriteLine("Withdrew {0:C} --- ", amount);
            Console.WriteLine(" Balance = {0:C}", this.Balance);
            Console.WriteLine(" Status = {0}\n",
              this.State.GetType().Name);
        }

        public void PayInterest() {
            state.PayInterest();
            Console.WriteLine("Interest Paid --- ");
            Console.WriteLine(" Balance = {0:C}", this.Balance);
            Console.WriteLine(" Status = {0}\n",
              this.State.GetType().Name);
        }
    }
}
